﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
//using dotEngine.Maths;

namespace Engine
{
    /// <summary>
    /// Updates the gamePad for every connected controller once per Update().
    /// </summary>
    public sealed class GamePadComponent
    {

        private readonly GamePadState[] states;
        private readonly GamePadState[] prevStates;

        private readonly Timer[] stepTimers = new Timer[4];

        #region Constants
        /// <summary>
        /// How much a stick must be moved to be felt as a step.
        /// </summary>
        private const float StepThreshold = 0.5f;

        /// <summary>
        /// How long a stick must be moved to be felt as a step
        /// </summary>
        private const float StepTime = 0.22f;

        private const float zeroThreshold = 0.15f;
        private const float zeroThresholdSquared = zeroThreshold * zeroThreshold;

        #endregion

        private struct Timer
        {
            // Left Stick and D-Pad
            public float Left;

            // Right Stick
            public float Right;
        }

        #region Shake Factors

        private struct ShakeFactor
        {
            public float shakeFactor;
            public float shakeFactorWearSpeed;
        }

        private ShakeFactor[] leftShakeFactors;
        private ShakeFactor[] rightShakeFactors;

        const float shakeFactorWear = 0.2f;
        const float shakeFactorWearAccel = 4.0f;

        #endregion


        #region Constructor
        public GamePadComponent()
        {
            states = new GamePadState[4];
            prevStates = new GamePadState[4];


            leftShakeFactors = new ShakeFactor[4];
            rightShakeFactors = new ShakeFactor[4];
        }
        #endregion

        #region Is Controller Connected

        public bool IsConnected(PlayerIndex player)
        {
            return states[(int)player].IsConnected;
        }

        public bool IsJustConnected(PlayerIndex player)
        {
            int p = (int)player;
            return states[p].IsConnected && !prevStates[p].IsConnected;
        }

        public bool IsJustDisconnected(PlayerIndex player)
        {
            int p = (int)player;
            return !states[p].IsConnected && prevStates[p].IsConnected;
        }

        #endregion

        #region Is Button Just Down
        /// <summary>
        /// True if the button was just pressed by the given player.
        /// </summary>
        public bool IsButtonJustDown(PlayerIndex player, Buttons button)
        {
            bool v = false;
            int i = (int)player;
            if (states[i].IsConnected)
            {
                v = states[i].IsButtonDown(button) && prevStates[i].IsButtonUp(button);
            }
            return v;
        }
        #endregion

        #region Is Button Just Down By Any Player
        /// <summary>
        /// True if the button was just pressed by any player.
        /// </summary>
        public bool IsButtonJustDownByAny(Buttons button)
        {
            bool v = false;
            for (int i = 0; i < 4; i++)
            {
                if (states[i].IsConnected)
                {
                    v |= states[i].IsButtonDown(button) && prevStates[i].IsButtonUp(button);
                }
            }
            return v;
        }
        #endregion

        #region Is Button Down/Up
        /// <summary>
        /// True if the button is pressed by the given player
        /// </summary>
        public bool IsButtonDown(PlayerIndex player, Buttons button)
        {
            return states[(int)player].IsButtonDown(button);
        }

        public bool IsButtonDownByAny(Buttons button)
        {
            bool v = false;
            for (int i = 0; i < 4; i++)
            {
                if (states[i].IsConnected)
                {
                    v |= states[i].IsButtonDown(button);
                }
            }
            return v;
        }

        public bool IsButtonDownByAny(Buttons button, out PlayerIndex playerIndex)
        {
            bool v = false;
            playerIndex = PlayerIndex.One;

            for (int i = 0; i < 4; ++i)
            {
                if (states[i].IsConnected)
                {
                    v |= states[i].IsButtonDown(button);

                    playerIndex = v ? (PlayerIndex)i : PlayerIndex.One;
                }
            }

            return v;
        }

        /// <summary>
        /// True if the button is pressed by the given player
        /// </summary>
        public bool IsButtonUp(PlayerIndex player, Buttons button)
        {
            return states[(int)player].IsButtonUp(button);
        }

        public bool IsButtonUpByAny(Buttons button)
        {
            bool v = false;
            for (int i = 0; i < 4; i++)
            {
                if (states[i].IsConnected)
                {
                    v |= states[i].IsButtonUp(button);
                }
            }
            return v;
        }

        #endregion

        #region Get Left / Right Triggers

        /// <summary>
        /// Returns the amount that the Left analog trigger
        /// is pushed. 1f: fully pushed. 0f: Not pushed.
        /// Beware that if the controller is disconnected it will always return 0f.
        /// </summary>
        /// <param name="player"></param>
        /// <returns></returns>
        public float GetLeftTrigger(PlayerIndex player)
        {

            float amount = 0;
            int i = (int)player;
            if (states[i].IsConnected)
            {
                amount = states[i].Triggers.Left;
            }
            return amount;
        }

        /// <summary>
        /// Returns the amount that the Right analog trigger
        /// is pushed. 1f: fully pushed. 0f: Not pushed.
        /// Beware that if the controller is disconnected it will always return 0f.
        /// </summary>
        /// <param name="player"></param>
        /// <returns></returns>
        public float GetRightTrigger(PlayerIndex player)
        {

            float amount = 0;
            int i = (int)player;
            if (states[i].IsConnected)
            {
                amount = states[i].Triggers.Right;
            }
            return amount;
        }


        #endregion

        #region Get Sticks
        /// <summary>
        /// Returns the thumbsticks status from a given player.
        /// </summary>
        public Vector2 GetLeftStick(PlayerIndex player)
        {
            return states[(int)player].ThumbSticks.Left;
        }
        /// <summary>
        /// Returns the thumbsticks status from a given player.
        /// </summary>
        public Vector2 GetRightStick(PlayerIndex player)
        {
            return states[(int)player].ThumbSticks.Right;
        }
        #endregion

        #region Get DPad
        /// <summary>
        /// Returns a Vector2 (-1, 1) representing the state of the DPad.
        /// </summary>
        public Vector2 GetDPad(PlayerIndex player)
        {
            GamePadDPad dpad = states[(int)player].DPad;
            return new Vector2(dpad.Left == ButtonState.Pressed ? -1 : (dpad.Right == ButtonState.Pressed ? 1 : 0),
                               dpad.Down == ButtonState.Pressed ? -1 : (dpad.Up == ButtonState.Pressed ? 1 : 0));
        }
        #endregion

        // To move in menus.
        #region Get Left / Right Step
        /// <summary>
        /// Use this to control a menu with the left side of the
        /// controller Left ThumbStick and D-Pad. Returns a step
        /// (0 or 1) in controlled intervals.
        /// </summary>
        public Vector2 GetLeftStep(PlayerIndex player)
        {
            Vector2 step = GetLeftStick(player) + GetDPad(player);

            // Horizontal step
            if (Math.Abs(step.X) < StepThreshold)
                step.X = 0;
            else
                step.X = (step.X > 0 ? 1 : -1);

            // Vertical step
            if (Math.Abs(step.Y) < StepThreshold)
                step.Y = 0;
            else
                step.Y = (step.Y > 0 ? 1 : -1);

            if (stepTimers[(int)player].Left >= StepTime)
            {
                stepTimers[(int)player].Left = 0;
                return step;
            }

            return Vector2.Zero;
        }

        public Vector2 GetLeftStepByAny()
        {
            Vector2 result = Vector2.Zero;
            Vector2 accumulation = Vector2.Zero;
            for (int i = 0; i < 4; i++)
            {
                accumulation += GetLeftStep((PlayerIndex)i);
            }

            result.X = Math.Sign(accumulation.X);
            result.Y = Math.Sign(accumulation.Y);

            return result;

        }

        /// <summary>
        /// Use this to control a menu with the left side of the
        /// controller Right ThumbStick. Returns a step
        /// (0 or 1) in controlled intervals.
        /// </summary>
        public Vector2 GetRightStep(PlayerIndex player)
        {
            Vector2 step = GetRightStick(player);

            // Horizontal step
            if (Math.Abs(step.X) < StepThreshold)
                step.X = 0;
            else
                step.X = (step.X > 0 ? 1 : -1);

            // Vertical step
            if (Math.Abs(step.Y) < StepThreshold)
                step.Y = 0;
            else
                step.Y = (step.Y > 0 ? 1 : -1);

            if (stepTimers[(int)player].Right >= StepTime)
            {
                stepTimers[(int)player].Right = 0;
                return step;
            }

            return Vector2.Zero;
        }

        public Vector2 GetRightStepByAny()
        {
            Vector2 result = Vector2.Zero;
            Vector2 accumulation = Vector2.Zero;
            for (int i = 0; i < 4; i++)
            {
                accumulation += GetRightStep((PlayerIndex)i);
            }

            result.X = Math.Sign(accumulation.X);
            result.Y = Math.Sign(accumulation.Y);

            return result;

        }

        #endregion

        #region Get Left / Right Sticks Quadrant

        //public StickQuadrant GetLeftQuadrant(PlayerIndex playerIndex)
        //{
        //    return leftStickQuadrantContainers[(int)playerIndex].stickQuadrant;
        //}

        /// <summary>
        /// Gets the Stick's Quadrant that falls within an angle threshold.
        /// Originally the quadrants are divided in four 90° degrees quadrants.
        /// Has to be bigger than 0 and less than 45.
        /// </summary>
        /// <param name="playerIndex"></param>
        /// <param name="angleThreshold"></param>
        /// <returns></returns>
        //public StickQuadrant GetLeftQuadrant(PlayerIndex playerIndex, float angleThreshold)
        //{

        //    float currentAngle = leftStickQuadrantContainers[(int)playerIndex].angle;

        //    StickQuadrant currentQuadrant = leftStickQuadrantContainers[(int)playerIndex].stickQuadrant;
        //    if (currentQuadrant == StickQuadrant.None)
        //        return currentQuadrant;
        //    else
        //    {
        //        float bottomAngle = MathHelper.PiOver2 * ((int)currentQuadrant);
        //        float topAngle = MathHelper.PiOver2 * ((int)(currentQuadrant + 1));
        //        if (currentAngle >= bottomAngle + angleThreshold && currentAngle < topAngle - angleThreshold)
        //            return currentQuadrant;
        //        else
        //            return StickQuadrant.None;
        //    }
        //}

        ///// <summary>
        ///// Gets the Stick's Quadrant that falls within an angle threshold.
        ///// Originally the quadrants are divided in four 90° degrees quadrants.
        ///// Has to be bigger than 0 and less than 45.
        ///// </summary>
        ///// <param name="playerIndex"></param>
        ///// <param name="angleThreshold"></param>
        ///// <returns></returns>
        //public StickQuadrant GetRightQuadrant(PlayerIndex playerIndex)
        //{
        //    return rightStickQuadrantContainers[(int)playerIndex].stickQuadrant;
        //}

        ///// <summary>
        ///// Gets the Stick's Quadrant that falls within an angle threshold.
        ///// Originally the quadrants are divided in four 90° degrees quadrants.
        ///// Has to be bigger than 0 and less than 45.
        ///// </summary>
        ///// <param name="playerIndex"></param>
        ///// <param name="angleThreshold"></param>
        ///// <returns></returns>
        //public StickQuadrant GetRightQuadrant(PlayerIndex playerIndex, float angleThreshold)
        //{

        //    float currentAngle = rightStickQuadrantContainers[(int)playerIndex].angle;

        //    StickQuadrant currentQuadrant = rightStickQuadrantContainers[(int)playerIndex].stickQuadrant;
        //    if (currentQuadrant == StickQuadrant.None)
        //        return currentQuadrant;
        //    else
        //    {
        //        float bottomAngle = MathHelper.PiOver2 * ((int)currentQuadrant);
        //        float topAngle = MathHelper.PiOver2 * ((int)(currentQuadrant + 1));
        //        if (currentAngle >= bottomAngle + angleThreshold && currentAngle < topAngle - angleThreshold)
        //            return currentQuadrant;
        //        else
        //            return StickQuadrant.None;
        //    }
        //}

        #endregion

        #region Get Left / Right Shake Factors

        public float GetLeftShakeFactor(PlayerIndex playerIndex)
        {
            return leftShakeFactors[(int)playerIndex].shakeFactor;
        }

        public float GetRightShakeFactor(PlayerIndex playerIndex)
        {
            return rightShakeFactors[(int)playerIndex].shakeFactor;
        }

        #endregion

        #region Reset Left / Right Shake Factors
        public void ResetLeftShakeFactor(PlayerIndex index)
        {
            leftShakeFactors[(int)index].shakeFactor = 0;
            leftShakeFactors[(int)index].shakeFactorWearSpeed = 0;
        }

        public void ResetRightShakeFactor(PlayerIndex index)
        {
            rightShakeFactors[(int)index].shakeFactor = 0;
            rightShakeFactors[(int)index].shakeFactorWearSpeed = 0;
        }
        #endregion

        #region Update
        /// <summary>
        /// Update the states.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            #region Left Stick / DPad / Right Stick Timers (Steps)
            for (int i = 0; i < 4; i++)
            {
                prevStates[i] = states[i];
                states[i] = GamePad.GetState((PlayerIndex)i);

                // Left Stick/DPad timer
                if (states[i].ThumbSticks.Left.Length() > StepThreshold ||
                    GetDPad((PlayerIndex)i).Length() >= 1)
                {
                    stepTimers[i].Left += dt;
                }
                else
                    stepTimers[i].Left = StepTime - 0.001f;
                // Right Stick timer
                if (states[i].ThumbSticks.Right.Length() > StepThreshold)
                {
                    stepTimers[i].Right += dt;
                }
                else
                    stepTimers[i].Right = StepTime - 0.001f;
            }
            #endregion

            #region Calculate Shake Factors

            for (int i = 0; i < 4; i++)
            {
                #region Left Shake Factors
                {
                    //Against
                    leftShakeFactors[i].shakeFactor -= dt * shakeFactorWear * (1 + leftShakeFactors[i].shakeFactorWearSpeed);

                    //Towards Shake Factor
                    Vector2 currentMinusPrevious = Vector2.Zero;
                    currentMinusPrevious.X = states[i].ThumbSticks.Left.X - prevStates[i].ThumbSticks.Left.X;
                    currentMinusPrevious.Y = states[i].ThumbSticks.Left.Y - prevStates[i].ThumbSticks.Left.Y;
                    float shakeFactorAdd = currentMinusPrevious.Length() * dt;

                    if (shakeFactorAdd < zeroThreshold * 0.02f)
                        leftShakeFactors[i].shakeFactorWearSpeed += shakeFactorWearAccel * dt;
                    else
                    {
                        leftShakeFactors[i].shakeFactorWearSpeed = 0;
                        leftShakeFactors[i].shakeFactor += shakeFactorAdd;
                    }

                    if (leftShakeFactors[i].shakeFactor < 0)
                    {
                        leftShakeFactors[i].shakeFactorWearSpeed = 0;
                        leftShakeFactors[i].shakeFactor = 0;
                    }
                }
                #endregion

                #region Right Shake Factors
                {
                    //Against
                    rightShakeFactors[i].shakeFactor -= dt * shakeFactorWear * (1 + rightShakeFactors[i].shakeFactorWearSpeed);

                    //Towards Shake Factor
                    Vector2 currentMinusPrevious = Vector2.Zero;
                    currentMinusPrevious.X = states[i].ThumbSticks.Right.X - prevStates[i].ThumbSticks.Right.X;
                    currentMinusPrevious.Y = states[i].ThumbSticks.Right.Y - prevStates[i].ThumbSticks.Right.Y;
                    float shakeFactorAdd = currentMinusPrevious.Length() * dt;

                    if (shakeFactorAdd < zeroThreshold * 0.02f)
                        rightShakeFactors[i].shakeFactorWearSpeed += shakeFactorWearAccel * dt;
                    else
                    {
                        rightShakeFactors[i].shakeFactorWearSpeed = 0;
                        rightShakeFactors[i].shakeFactor += shakeFactorAdd;
                    }

                    if (rightShakeFactors[i].shakeFactor < 0)
                    {
                        rightShakeFactors[i].shakeFactorWearSpeed = 0;
                        rightShakeFactors[i].shakeFactor = 0;
                    }
                }
                #endregion
            }

            #endregion

        }
        #endregion

    }

}
